package com.zhijian.warehouse.manage.service.impl;


import com.zhijian.BusinessException;
import com.zhijian.Constant;
import com.zhijian.response.enums.ResponseCodeEnum;
import com.zhijian.util.*;
import com.zhijian.warehouse.manage.config.access.SystemConfig;
import com.zhijian.warehouse.manage.exception.BusinessCommonException;
import com.zhijian.warehouse.manage.service.ItsmService;
import com.zhijian.warehouse.manage.util.AssertCommonUtil;
import com.zhijian.warehouse.manage.util.FileUtil;
import com.zhijian.warehouse.manage.util.cmmand.CommandResult;
import com.zhijian.warehouse.manage.util.cmmand.CommandUtils;
import com.zhijian.warehouse.manage.util.cmmand.CompressUtil;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Version 1.0.1
 */
@Slf4j
@Service
public class ItsmServiceImpl implements ItsmService {
    @Autowired
    private SystemConfig systemConfig;

    public static void main(String[] args) throws Exception {
        String startTime = "2024-11-28 00:00";
        String endTime = "2024-11-30 11:00";
        String jarFileName = "warehouse-manage-site-1.0.jar";
        String webFileName = "dist.tar.gz";
        String apkFileName = "UHF-serial_v1.3.7.apk";
        String upgradeDEK = "nh9Pv5KoW98=";
        Map<String, Object> jarFileNameData = MapBuilderUtil.builder().put("start_", startTime).put("end_", endTime).put("file_", jarFileName).build();
        Map<String, Object> webFileNameData = MapBuilderUtil.builder().put("start_", startTime).put("end_", endTime).put("file_", webFileName).build();
        Map<String, Object> apkFileNameData = MapBuilderUtil.builder().put("start_", startTime).put("end_", endTime).put("file_", apkFileName).build();
        System.out.println(EncryptUtil.encrypt(JsonUtil.toString(jarFileNameData), upgradeDEK, Constant.ALGORITHM));
        System.out.println(EncryptUtil.encrypt(JsonUtil.toString(webFileNameData), upgradeDEK, Constant.ALGORITHM));
        System.out.println(EncryptUtil.encrypt(JsonUtil.toString(apkFileNameData), upgradeDEK, Constant.ALGORITHM));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean upApk(MultipartFile file, String sk) {
        AssertUtil.notNull(sk, "升级失败，密钥错误");
        String fileName = file.getOriginalFilename();
        //获取文件名，salt验证sk
        try {
            if (!fileName.endsWith(".apk"))
                AssertCommonUtil.check(true, "上传失败，当前仅支持apk文件");
            //核对密钥
            checkSK(sk, fileName);
            //上传文件到目录下(后端触发脚本，前端文件解压)
            String upgradePath = systemConfig.getUpgrade().getApkPath();
            //保存到本地
            File apkFileLocal = FileUtil.saveLocalFile(upgradePath, fileName, file, true);
            AssertCommonUtil.check(BeanUtil.isEmpty(apkFileLocal), "apk上传失败，请联系管理员");
        } catch (BusinessCommonException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("{}| upgrade file error", fileName, ex);
            AssertCommonUtil.check(true, "文件上传失败");
        }
        return true;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean upgrade(MultipartFile file, String sk) {
        AssertUtil.notNull(sk, "升级失败，密钥错误");
        String fileName = file.getOriginalFilename();
        //获取文件名，salt验证sk
        if (!fileName.endsWith(".jar") && !fileName.endsWith(".tar.gz"))
            throw new BusinessException(ResponseCodeEnum.UPLOAD_FAIL.code, "升级失败，当前仅支持jar、tar.gz文件");
        //核对密钥
        checkSK(sk, fileName);
        //上传文件到目录下(后端触发脚本，前端文件解压)
        String upgradePath = null;
        if (fileName.endsWith(".jar")) {
            upgradePath = systemConfig.getUpgrade().getJarPath();
        } else {
            upgradePath = systemConfig.getUpgrade().getWebPath();
        }
        try {
            //移动旧文件
            File sfile = new File(upgradePath + fileName);
            if (sfile.exists()) {
                File destFile = new File(upgradePath + fileName + ".bak." + DateUtil.getTime(DateUtil.DATE_FORMAT_SYMD));
                if (destFile.exists())
                    destFile.delete();
                FileUtils.moveFile(sfile, destFile);
            }
            //保存新文件
            long startTime = System.currentTimeMillis();
            sfile = FileUtil.saveLocalFile(upgradePath, fileName, file, true);
            if (fileName.endsWith(".jar")) {
                //触发启动脚本
                CommandResult result = CommandUtils.exec(systemConfig.getUpgrade().getJarShell(), 90000);//默认60秒超时
                log.info("{}| upgrade file and exec comman end {}, times {}", fileName, result, (System.currentTimeMillis() - startTime));
                if (result != null && BeanUtil.isNotEmpty(result.getError()))
                    AssertCommonUtil.check(true, "升级失败，请查看日志详细信息");
            } else {
                //直接解压覆盖
                boolean flag = CompressUtil.decompress(upgradePath + fileName, upgradePath);
                log.info("{}| upgrade and decompress end {}, times {}", fileName, flag, (System.currentTimeMillis() - startTime));
                if (!flag)
                    AssertCommonUtil.check(true, "升级失败，请查看日志详细信息");
            }
        } catch (BusinessCommonException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("{}| upgrade file error", fileName, ex);
            AssertCommonUtil.check(true, "升级失败，请查看日志详细信息");
        }
        return true;
    }

    /**
     * 生成密钥
     *
     * @param startTime 密钥有效期：开始时间
     * @param endTime   密钥有效期：过期时间
     * @param fileName  加密文件名称
     * @return
     * @throws Exception
     */
    private String createSK(String startTime, String endTime, String fileName) throws Exception {
        Map<String, Object> data = MapBuilderUtil.builder().put("start_", startTime).put("end_", endTime).put("file_", fileName).build();
        return EncryptUtil.encrypt(JsonUtil.toString(data), systemConfig.getUpgrade().getDek(), Constant.ALGORITHM);
    }

    @Override
    public String puCreateSK(String startTime, String endTime, String fileName) throws Exception {
        return createSK(startTime, endTime, fileName);
    }

    /**
     * 校验密钥
     *
     * @param sk
     * @param fileName
     */
    private void checkSK(String sk, String fileName) {
        try {
            String skData = EncryptUtil.decrypt(sk, systemConfig.getUpgrade().getDek(), Constant.ALGORITHM);
            Map<String, Object> skMap = JsonUtil.toMap(skData);
            String endTime = BeanUtil.getStr4Map("end_", skMap, null);
            if (BeanUtil.isEmpty(endTime) || DateUtil.fmtTime(endTime + ":00", "yyyy-MM-dd HH:mm").getTime() < System.currentTimeMillis()) {
                log.error("{}| upgrade file error, expire time {}", fileName, endTime);
                AssertCommonUtil.check(true, "升级失败，密钥已过期，请联系管理员");
            }
            String newSK = createSK(BeanUtil.getStr4Map("start_", skMap, null), endTime, fileName);
            if (BeanUtil.isEmpty(newSK) || !newSK.equals(sk))
                AssertCommonUtil.check(true, "升级失败，密钥错误，请确认后重试");
        } catch (BusinessCommonException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("{}| upgrade file error, not match sk", fileName, ex);
            AssertCommonUtil.check(true, "文件解析失败");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Map<String, Object>> selectLogs(String date) {
        List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
        //慢查询日志
        List<String> slowLogs = listFiles(systemConfig.getLogPath(), "slowLog." + (BeanUtil.isNotEmpty(date) ? date : "")).stream()
                .sorted(Comparator.reverseOrder()).limit(3).collect(Collectors.toList());
        slowLogs.forEach(p -> {
            result.add(MapBuilderUtil.builder().put("fileName", p).build());
        });
        //日志
        List<String> logs = listFiles(systemConfig.getLogPath(), "catalina." + (BeanUtil.isNotEmpty(date) ? date : "")).stream()
                .sorted(Comparator.reverseOrder()).limit(6).collect(Collectors.toList());
        logs.forEach(p -> {
            result.add(MapBuilderUtil.builder().put("fileName", p).build());
        });
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void downloadLog(String fileName, HttpServletResponse response) {
        try {
            File file = new File(systemConfig.getLogPath() + File.separator + fileName);
            if (!file.exists() || !file.isFile() || !file.canRead())
                AssertCommonUtil.check(true, "文件访问失败，请确认文件信息");
            @Cleanup
            InputStream ins = new FileInputStream(file);
            //转换输出流
            response.setContentType("application/force-download");
            response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("gb2312"), "ISO-8859-1"));
            @Cleanup OutputStream ous = response.getOutputStream();
            IOUtils.copy(ins, ous);
            ous.flush();
        } catch (Exception ex) {
            log.error("{}| download error", fileName, ex);
            AssertCommonUtil.check(true, "文件下载失败");
        }
    }

    private List<String> listFiles(String path, String... prefix) {
        List<String> files = new ArrayList<String>();
        if (!path.endsWith(File.separator))
            path = path + File.separator;
        File dir = new File(path);
        //非目录返回
        if (!(dir.exists() && dir.isDirectory()))
            return files;
        File[] childs = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File tmp) {
                if (BeanUtil.isNotEmpty(prefix))
                    return tmp.isFile() && tmp.getName().startsWith(prefix[0]);
                return tmp.isFile();
            }
        });
        for (File file : childs) {
            files.add(file.getName());
        }
        return files;
    }

}
